BreakAid v0.01
User Guide

23rd August 2016
by Rick Murray

Contents

Précis

What does BreakAid do?

Using BreakAid with an assembler program

Using BreakAid with a C program

Using BreakAid with a BASIC program

BreakAid SWIs

BreakAid_Attach &59A40

BreakAid_Remove &59A41

Caveats

Using BreakAid

Continue

Quit

Dump registers

Change register

System Information

Change flags

Display memory

Display stack

PC disassembly

Test program

Support

Thanks, credits, etc...

Précis

There are generally three ways of debugging applications within RISC OS.

The first is to use DDT, the interactive debugger. This presupposes you have the Desktop Development Environment, build a "debug" version of the application (containing special debug data), and then step into the program and set up breakpoints etc. For complicated problems, this approach may be invaluable, however it is rather tedious. DDT also suffers from two fundamental problems inherent to how it works. The first is that the "debug" code has all of the optimisations disabled. This is unlikely to make much difference if you're stepping through the program in lines of C code, however should you try to compare the assembler you will find it can often be quite a bit different to the normal compiled version. Sometimes this may be important in how a bug shows itself. Secondly, DDT provides a "sanitised" environment, which means that a piece of code that expects a variable to be initialised as zero (but isn't) will work perfectly within DDT but fail in real life.

The second method is to use the built-in Debugger module. While this allows for setting up proper breakpoints and showing memory contents and registers, it is remarkably basic - you cannot, for instance, change the contents of a register or the flags, which seems like a bizarre omission to me.

The third method is the one favoured since the dawn of affordable computing. You simply insert commands to output something to the screen. In RISC OS, this can be achieved with a simple SWI call that corrupts no registers - SWI (256 plus the ASCII code), for instance:

  SWI 256 + 'A'
  SWI 256 + 'B'
  SWI 256 + 'C'

And then you just run the bugged code and see how much gets printed before it goes wrong. Keep playing around like that and you'll soon pinpoint the exact line that is failing. I've been programming since the BBC Micro back in the mid eighties and I feel that this method is underrated!
Problem is, while outputting stuff to the screen can narrow things down, it can't tell you what is actually going on - it requires inserted code to show the values of registers, for instance, and being able to change them is non-trivial.

Enter a fourth option.

BreakAid.

What does BreakAid do?

BreakAid leverages the barely-used OS "breakpoint" system. This is the SWI "OS_BreakPt" which has no purpose other than to call the "breakpoint handler". This is not a breakpoint in the same sense as the Debugger module or DDT, you cannot specify instructions to break at, you must explicitly call the OS_BreakPt SWI when you want the trap to be taken.

And when this SWI is called? BreakAid will kick into action and take over from your program, offering you a dump of the registers followed by a menu of choices.

Unlike the Debugger, you cannot modify the executing program (or memory) but you can see what is there - either by disassembling around PC (the current instruction) or by looking at a dump of memory.
More importantly, you can see what the registers are. You can modify the registers, and you can modify the flags.
It is also possible to alter the program execution by modifying the value in R15.

To give you an example, your program causes an "abort on data transfer". You've tracked it down to an LDM instruction. Nothing in the code looks untoward, however with the aid of BreakAid you can clearly see that the base register for the LDM is &90549439 (2,421,462,073). Well, not only is that not a word aligned address, it is also a value that is WAY out of range. So then you need to work back to see what's going wrong in calculating the base address.

Using BreakAid with an assembler program

To use BreakAid in an assembler program, insert these two lines at the part you wish to examine:

   SWI    &59A40 ; XBreakAid_Attach
   SWIVC  &17    ; OS_BreakPt

The first SWI will tell BreakAid to "attach" itself as the BreakPt handler. This is necessary as RISC OS has a habit of resetting this to the system breakpoint handler ALL the time. However since we are calling the X form of the SWI, we can double up to test if the BreakAid module is even loaded. If it is not, an error will be returned (V set), so we can then call the OS_BreakPt SWI only if BreakAid is available.

Using BreakAid with a C program

C programs can also be looked at with BreakAid. If you are using the DDE, you can use the following to invoke BreakAid:

   __asm
   {
      SWI   0x59A40, {}, {PSR}, {}   // XBreakAid_Attach
      SWIVC 0x17, {}, {}, {}         // OS_BreakPt
   }

For other compilers (such as gcc) you will need to refer to the documentation to see if it is possible to inline assembler.

It is, obviously, harder to poke around a C program.

Using BreakAid with a BASIC program

Don't.
No, seriously. Don't.
BASIC is a stack-based interpreter, which also happens to transfer values passed into a SYS call to R0-R9. And if you don't provide any values? It'll set them to zero.
You could, alternatively, try to sidestep that with a short bit of assembler to call the two SWIs given above, using the CALL command. And you'll see that the registers bear utterly no resemblance to anything (when I tried, most of them were set to '1').

So, technically BreakAid can be used with BASIC. However, in reality there's no point doing so.

BreakAid SWIs

BreakAid_Attach &59A40

This SWI will ensure that BreakAid is the current BreakPt handler. You should call this prior to calling OS_BreakPt.
It is safe to call this multiple times.

BreakAid_Remove &59A41

This SWI restores the previous BreakPt handler. It should be called when your application exits.

Caveats

BreakAid is currently intended for NON multitasking programs. This is because it outputs to the VDU, which might have odd effects when used within the Desktop world. [this will be looked at soon]

BreakAid is only intended for examining USR mode code. It cannot be used in SVC mode for three reasons:

Since SWIs themselves run in SVC mode, R14 will be trashed.

The OS_BreakPt code in RISC OS flattens the SVC stack. In English, that means R13 is reset to its default position and anything stacked is thus discarded.

A bug in RISC OS (fixed in June 2016, a mere 14 years after the release of the Iyonix!) meant that calling OS_BreakPt in SVC mode would cause an exception.

BreakAid is intended for use with application code. Some of the functionality restricts addresses in the range &8000-appslot.

Using BreakAid

When your application calls OS_BreakPt, your program will be paused, the current registers will be displayed, and a menu will appear:

Breakpoint at &0000801C:
  R0  = &00008049   R1  = &00408000   R2  = &203C7BE4
  R3  = &00000068   R4  = &00008000   R5  = &3004575C
  R6  = &00000000   R7  = &00000068   R8  = &0000000A
  R9  = &300482F0   R10 = &FA208000   R11 = &FA207FA0
  R12 = &80000000   R13 = &00407FF0   R14 = &FC04D78C
  PC  = &0000801C   Flags: nzCv (&20000110)

[C]ontinue, [Q]uit, [D]ump registers, change [R]egister, sys[I]nfo
change [F]lags, display [M]emory, display [S]tack, PC dis[A]ssembly
Choice?

Continue

This will restore the state of your program and jump to the instruction following the call to OS_BreakPt.

Quit

This will call OS_Exit to immediately terminate your program.
Note that this is not a tidy exit, so if there are files currently open, they will remain open.

Dump registers

This dumps the registers. Useful to confirm changes.

  R0  = &00008049   R1  = &00408000   R2  = &203C7BE4
  R3  = &00000068   R4  = &00008000   R5  = &3004575C
  R6  = &00000000   R7  = &00000068   R8  = &0000000A
  R9  = &300482F0   R10 = &FA208000   R11 = &FA207FA0
  R12 = &80000000   R13 = &00407FF0   R14 = &FC04D78C
  PC  = &0000801C   Flags: nzCv (&20000110)

Change register

This prompts you for a register to change, and then what to change it to. Note at all registers are referenced by number, so SP=13, LR=14, and PC=15.

Here is an example of resetting the application's stack pointer by discarding the four stacked values:

Change R13
R13 is &00407FF0, or 4227056.
Change it to (prefix hex with &) : &408000
Register R13 changed to &00408000, or 4227072.

And if we then dump the registers, we can see that R13 has been altered:

  R0  = &00008049   R1  = &00408000   R2  = &203C7BE4
  R3  = &00000068   R4  = &00008000   R5  = &3004575C
  R6  = &00000000   R7  = &00000068   R8  = &0000000A
  R9  = &300482F0   R10 = &FA208000   R11 = &FA207FA0
  R12 = &80000000   R13 = &00408000   R14 = &FC04D78C
  PC  = &0000801C   Flags: nzCv (&20000110)

System Information

Provides some basic information on the system:

System memory       219 Mbytes total, 185 Mbytes free.
Application memory  4 Mbytes
Processor CPUID     &410FB767, alignment exceptions
Vectors             Low
Running in a TaskWindow.

The last line, obviously, only appears when using the TaskWindow.

Change flags

This permits you to alter the decision-making processor flags - N, Z, C, and V.
You are told the current state of the flags, then you are asked to press S to specify Set or C to specify Clear for each flag in turn. Like this:

Flags:
  Negative    : Clear
  Zero        : Clear
  Carry       : Set
  oVerflow    : Clear
Press [S]et or [C]lear, or [Return] to leave as-is.
  Negative ? Clear
  Zero     ? Set
  Carry    ? Clear
  oVerflow ? Set
Flags are now:
  Negative    : Clear
  Zero        : Set
  Carry       : Clear
  oVerflow    : Set

Display memory

This is akin to the *Memory command in the Debugger. Provide an address to see what's there:

Address (prefix hex with &) ? &8000

Address  :  3 2 1 0   7 6 5 4   B A 9 8   F E D C  :    ASCII Data
00008000 : EF000010  E1A0D001  E92D000F  EF059940  : ...ï.Ð á..-é@™.ï
00008010 : E28F0018  EF000002  EF000003  EF000017  : ...â...ï...ï...ï
00008020 : E28F0024  EF000002  EF000003  EF000011  : $..â...ï...ï...ï
00008030 : 756F6241  6F742074  6C616320  7262206C  : About to call br
00008040 : 706B6165  746E696F  00000000  75746552  : eakpoint....Retu
00008050 : 64656E72  6F726620  7262206D  706B6165  : rned from breakp
00008060 : 746E696F  00000000  E3A00000  E3530000  : oint...... ã..Sã
00008070 : D1A0F00E  E48C0004  E2533004  EAFFFFFB  : .ð Ñ..Œä.0Sâûÿÿê

Display stack

This will walk backwards up the stack displaying each stored value in turn:

00407FF0 : 201F66D4
00407FF4 : 00408000
00407FF8 : 203C7BE4
00407FFC : 00000068
** END **

It will display up to eight entries, and stop if it reaches the end of application space (which is the normal way that a stack is set up in a simple assembler application).

PC disassembly

This will attempt to disassemble ten instructions either side of the current Program Counter, however it is constrained to not disassemble below &8000 (as memory there doesn't exist on a high vector machine).
It uses the disassembler built into the Debugger module, the entire output is designed to look the same. The current location of PC is indicated by "->".

00008000 : ...ï : EF000010 : SWI     OS_GetEnv
00008004 : .Ð á : E1A0D001 : MOV     R13,R1
00008008 : ..-é : E92D000F : STMDB   R13!,{R0-R3}
0000800C : @™.ï : EF059A40 : SWI     BreakAid_Attach
00008010 : ...â : E28F0018 : ADR     R0,&00008030
00008014 : ...ï : EF000002 : SWI     OS_Write0
00008018 : ...ï : EF000003 : SWI     OS_NewLine
0000801C-> ...ï : EF000017 : SWI     OS_BreakPt
00008020 : $..â : E28F0024 : ADR     R0,&0000804C
00008024 : ...ï : EF000002 : SWI     OS_Write0
00008028 : ...ï : EF000003 : SWI     OS_NewLine
0000802C : ...ï : EF000011 : SWI     OS_Exit
00008030 : Abou : 756F6241 : STRVCB  R6,[PC,#-577]!     ; *** PC writeback
00008034 : t to : 6F742074 : SWIVS   &742074
00008038 :  cal : 6C616320 : STCVSL  CP3,C6,[R1],#-128
0000803C : l br : 7262206C : RSBVC   R2,R2,#&6C         ; ="l"
00008040 : eakp : 706B6165 : RSBVC   R6,R11,R5,ROR #2
00008044 : oint : 746E696F : STRVCBT R6,[R14],#-2415
00008048 : .... : 00000000 : ANDEQ   R0,R0,R0
0000804C : Retu : 75746552 : LDRVCB  R6,[R4,#-1362]!
00008050 : rned : 64656E72 : STRVSBT R6,[R5],#-3698

Test program

The information was taken from the following test program written directly in Zap (!).

   &8000  SWI     OS_GetEnv
    8004  MOV     R13, R1
    8008  STMFD   R13!,{R0-R3}
    800C  ADR     R0, &8030
    8010  SWI     OS_Write0
    8014  SWI     OS_NewLine
    8018  SWI     XBreakAid_Attach
    801C  SWIVC   OS_BreakPt
    8020  ADR     R0, &804C
    8024  SWI     OS_Write0
    8028  SWI     OS_NewLine
    802C  SWI     OS_Exit
    8030  DCS     "About to call breakpoint"
    8048  DCD     0
    804C  DCS     "Returned from breakpoint"
    8064  DCD     0

A very simple program demonstrating BreakAid in use.

Support

BreakAid is available from:

http://www.heyrick.co.uk/software/breakaid/

No "official" support is offered, however if you have a problem or a request you can contact me at:

heyrick 1973 -at- yahoo -dot- co -dot- uk

However, before contacting me please check to ensure that you are using the latest version of BreakAid.

Thanks, credits, etc...

Thanks to Castle and ROOL for the fact that RISC OS still exists today.

Thanks to Jeffrey for identifying and fixing the OS_BreakPt bug (among others!).
[https://www.riscosopen.org/viewer/revisions/logs?ident=1470681362-525659.html]

Thanks to Tank for fixing Zap to work on newer machines.

Thanks to the Raspberry Pi Foundation for a RISC OS machine that costs less than a meal out.

Thanks to CJE Micro's for the Pi itself, plus the RTC.

Thanks to Mystery Benefactor for the DDE. ;-)

Thanks to Colin Ferris for putting the idea in my head in the first place.
[https://www.riscosopen.org/forum/forums/4/topics/6366]

Thanks to Tetley for the brain fuel. Lots and lots of.

Thanks to Kalafina and Nightwish for the soundtrack.

Thanks to Mom, 'cos she'd be upset if I didn't. :-P

Finally, thanks to the many people who have come and gone and have provided me with help, ideas, or things to think about.